package com.my.app.servlet;

import com.my.app.beans.FileUploadBean;
import com.my.app.db.UploadFileDao;
import com.my.app.exception.InvalidExtNameException;
import com.my.app.utils.FileUploadAppProperties;
import com.my.app.utils.FileUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * Created by YJH on 2017/7/14 17:47.
 */
public class FileUploadServlet extends HttpServlet {

    //    private static final String FILE_PATH = "/WEB-INF/files/";
    private static final String FILE_PATH = "d:\\temp";
    private static final String TEMP_DIR = "d:\\temp\\tempDir";

    private UploadFileDao dao = new UploadFileDao();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");

        String path = null;

        ServletFileUpload upload = getServletFileUpload();

        try {
            //把需要上传的FileItem都放入到该Map    键：文件的待存放的路径，    值：对应的FileItem对象
            Map<String, FileItem> uploadFiles = new HashMap<>();

            //解析请求，得到FileItem集合
            List<FileItem> items = upload.parseRequest(request);

            //1.构建FileUploadBean集合,同时填充uploadFiles
            List<FileUploadBean> beans = buildFileUploadBeans(items, uploadFiles);

            //2.校验扩展名
            vaidateExtName(beans);

            //3.检验文件的大小：在解析时，已经校验了，我们只需通过异常得到结果

            //4.进行文件上传
            upload(uploadFiles);

            //5.把上传的信息保存到数据库中
            saveBeans(beans);

            //6.删除临时文件中的临时文件
            FileUtils.delAllFile(TEMP_DIR);

            path = "/app/success.jsp";

        } catch (Exception e) {
            e.printStackTrace();

            path = "/app/upload.jsp";
            request.setAttribute("message", e.getMessage());
        }

        request.getRequestDispatcher(path).forward(request, response);
    }


    /**
     * 保存到数据库中
     *
     * @param beans List<FileUploadBean>
     */
    private void saveBeans(List<FileUploadBean> beans) {
        dao.save(beans);
    }

    /**
     * 文件上传前的准备工作，得到filePath和InputStream
     *
     * @param uploadFiles Map<String, FileItem>
     */
    private void upload(Map<String, FileItem> uploadFiles) {
        for (Map.Entry<String, FileItem> uploadFile : uploadFiles.entrySet()) {
            String filePath = uploadFile.getKey();
            FileItem item = uploadFile.getValue();

            try {
                upload(filePath, item.getInputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 文件上传的IO方法
     *
     * @param filePath    String
     * @param inputStream InputStream
     * @throws IOException IO异常
     */
    private void upload(String filePath, InputStream inputStream) throws IOException {
        OutputStream out = new FileOutputStream(filePath);

        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }

        inputStream.close();
        out.close();

        System.out.println(filePath);
    }

    /**
     * 校验扩展名是否合法
     *
     * @param beans List<FileUploadBean>
     * @return boolean
     */
    private boolean vaidateExtName(List<FileUploadBean> beans) {
        String exts = FileUploadAppProperties.getInstance().getProperty("exts");
        List<String> extList = Arrays.asList(exts.split(","));

        for (FileUploadBean bean : beans) {
            String fileName = bean.getFileName();
            String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
            if (!extList.contains(extName)) {
                throw new InvalidExtNameException(fileName + "文件的扩展名不合法");
            }
        }
        return true;
    }

    /**
     * 利用传入FileItem的集合，构建FileUploadBean集合，同时填充uploadFiles
     * FileUploadBean对象封装了：id，fileName，filePath，fileDesc
     * uploadFiles：Map<String, FileItem>类型，存放文件域类型的FileItem. 键：待保存的文件的名字，值：FileItem对象
     * <p>
     * 构建过程：
     * 1.对传入的FileItem集合进行遍历，的到desc的Map，键：desc的fieldName(desc1, desc2...)，
     * 值：desc的输入的文本值
     * 2.对传入FileItem的集合进行遍历，的到文件域的那些FileItem对象，构建对应的key(desc1...)来获取其desc
     * 构建FileUploadBean对象，并填充beans和uploadFiles
     *
     * @param items List<FileItem>
     * @return List<FileUploadBean>
     */
    private List<FileUploadBean> buildFileUploadBeans(List<FileItem> items, Map<String, FileItem> uploadFiles) throws UnsupportedEncodingException {

        List<FileUploadBean> beans = new ArrayList<>();

        //1.遍历FileItem的集合，先得到desc的Map<String, String>，其中键：field(desc1, desc2...),值：表单域对应的字段值
        Map<String, String> descs = new HashMap<>();

        for (FileItem item : items) {
            //如果为表单域
            if (item.isFormField()) {
                descs.put(item.getFieldName(), item.getString("UTF-8"));
            }
        }

        //2.再遍历FileItem的集合，得到文件域的FileItem对象，
        //每个得到的FileItem对象都创建一个FileUploadBean对象，
        //得到fileName，构建filePath，从1的Map中得到当前的FileItem对应的那个desc
        //使用filedName后面的数字去匹配
        for (FileItem item : items) {
            if (!item.isFormField()) {
                String fieldName = item.getFieldName();
                String index = fieldName.substring(fieldName.length() - 1);

                String fileName = item.getName();
                String desc = descs.get("desc" + index);
                String filePath = getFilePath(fileName);

                FileUploadBean bean = new FileUploadBean(fileName, filePath, desc);
                beans.add(bean);

                uploadFiles.put(filePath, item);
            }
        }

        return beans;
    }

    /**
     * 根据给定的文件名构建一个随机的文件名
     * 1.构建的文件的文件名的扩展名个给定的文件的扩展名一致
     * 2.d:/temp
     * 3.利用Random和当前的系统时间构建随机的文件的名字
     *
     * @param fileName String
     * @return String
     */
    private String getFilePath(String fileName) {
        String extName = fileName.substring(fileName.lastIndexOf("."));
        Random random = new Random();
        int randomNum = random.nextInt(100000);
//        String filePath = getServletContext().getRealPath(FILE_PATH) + "\\" + System.currentTimeMillis() + randomNum + extName;
        String filePath = FILE_PATH + "\\" + System.currentTimeMillis() + randomNum + extName;
        return filePath;
    }

    /**
     * 构建ServletFileUpload对象
     * 从配置文件中读取
     *
     * @return ServletFileUpload
     */
    private ServletFileUpload getServletFileUpload() {
        String exts = FileUploadAppProperties.getInstance().getProperty("exts");
        String fileMaxSize = FileUploadAppProperties.getInstance().getProperty("file.max.size");
        String totalFileMaxSize = FileUploadAppProperties.getInstance().getProperty("total.file.max.size");
        System.out.println("exts = " + exts + ", fileMaxSize = " + fileMaxSize + ", totalFileMaxSize = " + totalFileMaxSize);


        //为基于磁盘的文件项创建工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();

        //设置工厂约束
        factory.setSizeThreshold(1024 * 500);
        File tempDir = new File(TEMP_DIR);
        factory.setRepository(tempDir);

        //创建一个新的文件上传处理器
        ServletFileUpload upload = new ServletFileUpload(factory);

        //设置整体请求大小约束
        upload.setSizeMax(Integer.parseInt(totalFileMaxSize));
        upload.setFileSizeMax(Integer.parseInt(fileMaxSize));

        return upload;
    }
}
